/*
* Copyright 2010 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/

package org.etherframework.wire.wirings
{
	import org.etherframework.core.Access;
	import org.etherframework.core.DomainAccess;
	import org.etherframework.core.IBridge;
	import org.etherframework.reflection.Accessor;
	import org.etherframework.reflection.Metadata;
	import org.etherframework.reflection.MetadataMember;
	import org.etherframework.reflection.Variable;
	import org.etherframework.wire.WiringData;
	import org.etherframework.wire.WiringUtil;
	
	import flash.utils.Dictionary;
	
	public class Mediation implements IWiring, IInjection
	{
		[Inspectable(defaultValue="local", enumeration="local,hierarchical,@hierarchical,hierarchical@,global")]
		public var access:String = "local";
		
		[Inspectable(defaultValue="single", enumeration="single,multiple")]
		public var domain:String = "single";
		
		public var type:String;
		
		public var member:Object;
		
		// it's not needed for regular metatag usage but could be 
		// useful for wiring injections due to lack of meta info.
		public var facade:String;
		
		private var bridge:IBridge;
		
		public function wire(name:String, target:Object, item:Object, data:WiringData):Boolean
		{
			if (!((item is Variable) || (item is Accessor)))
				return false;
			
			var metadatas:Array;
			var metadatasLen:int;
			var metadata:Metadata;
			var properties:Dictionary;
			var member:MetadataMember = item as MetadataMember;
			
			metadatas = member.metadata;
			metadatasLen = metadatas.length;
			this.member = (member.uri) ? 
				new QName(new Namespace(member.uri), member.name) : member.name;
			bridge = data.bridge;
			
			for (var i:int = 0; i < metadatasLen; ++i)
			{
				metadata = metadatas[i];
				
				if (metadata.name == name)
				{
					properties = WiringUtil.getProperties(metadata.properties);
					access = Access.getAccess(properties["access"]);
					domain = DomainAccess.getAccess(properties["domain"]);
					type = (properties["type"] == "multiple") ? "multiple" : "single";
					facade = properties["facade"];
					target[this.member] = getFacade(new MediationFacade(access, domain, type, bridge), facade || member.type);
					
					return true;
				}
			}
			
			return false;
		}
		
		public function unwire(target:Object):Boolean
		{
			try {
				if (target[member])
				{
					target[member] = null;
					return true;
				}
			} catch (e:*) {}
			
			return false;
		}
		
		public function inject(name:String, target:Object, data:WiringData):Boolean
		{
			try {
				if (!target[member])
				{
					bridge = data.bridge;
					access = Access.getAccess(access);
					domain = DomainAccess.getAccess(domain);
					target[member] = getFacade(new MediationFacade(access, domain, type, bridge), facade);
					
					return true;
				}
			} catch (e:*) {}
			
			return false;
		}
		
		private function getFacade(mfacade:MediationFacade, type:String):Object
		{
			var obj:Object = null;
			var w:Object = type ? bridge.util.getClass(type) : null;
			try {obj = (w) ? new w(mfacade) : mfacade;}catch(e:*){};
			
			return obj;
		}
		
	}
}

import org.etherframework.core.Access;
import org.etherframework.core.IBridge;
import org.etherframework.core.events.InteractionEvent;
import org.etherframework.wire.WiringUtil;

import flash.utils.Proxy;
import flash.utils.flash_proxy;

dynamic final class MediationFacade extends Proxy
{
	// local || hierarchical || global
	private var access:String;
	
	// single || multiple
	private var domain:String;
	
	// single || multiple
	private var type:String;
	
	private var bridge:IBridge;
	
	function MediationFacade(access:String, domain:String, type:String, bridge:IBridge)
	{
		super();
		
		this.access = access;
		this.domain = domain;
		this.type = type;
		this.bridge = bridge;
	}
	
	override flash_proxy function callProperty(methodName:*, ... args):* 
	{
		var result:*;
		var method:String = methodName.toString();
		
		var evt:InteractionEvent = new InteractionEvent(InteractionEvent.MEDIATION + method);
		evt.data = args;
		evt.result = [];
		bridge.dispatch(evt, access, domain);
		
		if (!evt.result.length)
			return null;
		
		return (type == "single") ? evt.result[0] : evt.result;
	}
	
	override flash_proxy function getProperty(name:*):* 
	{
		var result:* = null;
		
		if (name.toString() == "proxy")
		{
			return new ProxyResolver(access, domain, type, bridge);
		}
		
		var evt:InteractionEvent = new InteractionEvent(InteractionEvent.MEDIATION +name.toString());
		evt.data = [];
		evt.result = [];
		bridge.dispatch(evt, access, domain);
		
		if (!evt.result.length)
			return null;
			
		return (type == "single") ? evt.result[0] : evt.result;
		
	}
	
	override flash_proxy function setProperty(name:*, value:*):void 
	{
		var prop:String = name.toString();
		var evt:InteractionEvent = new InteractionEvent(InteractionEvent.MEDIATION + name.toString());
		evt.data = [value];
		
		bridge.dispatch(evt, access, domain);
	}
	
}

dynamic final class ProxyResolver extends Proxy
{
	private var access:String;
	
	private var domain:String;
	
	private var type:String;
	
	private var bridge:IBridge;
	
	function ProxyResolver(access:String, domain:String, type:String, bridge:IBridge)
	{
		super();
		this.access = access;
		this.domain = domain;
		this.type = type;
		this.bridge = bridge;
	}
	
	override flash_proxy function callProperty(methodName:*, ... args):*
	{
		return null;
	}
	
	override flash_proxy function getProperty(name:*):*
	{
		var items:Array = [];
		var result:* = null;
		var evt:InteractionEvent = new InteractionEvent(InteractionEvent.GET_PROXY + name.toString());
		evt.result = items;
		
		bridge.dispatch(evt, access, domain);
		
		if (items.length)
		{
			if ((items.length == 1) || (type == "single"))
			{
				result = items[0].target; 
			}
			else
			{
				result = new ProxyMemeber(items);
			}
		}
		
		return result;
	}
	
	override flash_proxy function setProperty(name:*, value:*):void
	{
		
	}
}

dynamic final class ProxyMemeber extends Proxy
{
	private var items:Array;
	
	function ProxyMemeber(items:Array)
	{
		super();
		this.items = items;
	}
	
	override flash_proxy function callProperty(methodName:*, ... args):* 
	{
		var result:Array = [];
		var method:String = methodName.toString();
		var max:int = items.length;
		
		try
		{
			for (var i:int = 0; i < max; ++i)
			{
				result.push(items[i].target[method].apply(null, args));
			}
		} catch (e:Error) {
			trace(e.toString());
		}
		
		return result;
	}
	
	override flash_proxy function getProperty(name:*):* 
	{
		var result:*;
		var prop:String = name.toString();
		
		switch (prop)
		{
			case "length":
			{
				result = items.length;
				break;
			}
			default:
			{
				var arr:Array = [];
				var max:int = items.length;
				
				try 
				{
					for (var i:int = 0; i < max; ++i)
					{
						arr.push(items[i].target[prop]);
					}
				} 
				catch (e:Error) {
					trace(e.toString());
				}
				
				result = arr;
			}
		}
		
		return result;
	}
	
	override flash_proxy function setProperty(name:*, value:*):void 
	{
		var prop:String = name.toString();
		var max:int = items.length;
		
		try 
		{
			for (var i:int = 0; i < max; ++i)
			{
				items[i].target[prop] = value;
			}
		} 
		catch (e:Error) {
			trace(e.toString());
		}
	}
}